package com.hhd.flow.engine.cmd.impl;

import com.alibaba.fastjson.JSONObject;
import com.hhd.flow.engine.cmd.PassTaskContext;
import com.hhd.flow.engine.utils.CommonUtils;
import com.hhd.flow.engine.utils.FlowUtils;
import com.hhd.flow.engine.vo.FlowModel;
import com.hhd.flow.engine.vo.FlowUser;
import com.hhd.flow.engine.vo.StartNode;
import com.hhd.flow.engine.vo.UserManualSelect;
import com.hhd.flow.entity.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;

import java.util.List;
import java.util.Objects;

@Setter
@Getter
@SuperBuilder(toBuilder = true)
public class StartProcessCommand extends FlowUserTaskChangeCommand<FlowProcessInstance> {
    private String formTypeId;          //单据类型
    private String formId;              //单据类型
    private String processDefinitionId; //流程定义id
    private UserManualSelect userManualSelect; //手动指定的审批人
    private JSONObject formModel;
    private String processTitle;

    //下面是运行时数据
    private FlowUser applyBy;           //发起人
    private FlowProcessDefinitionRef flowProcessDefinitionRef; //流程定义引用(用来做流程版本控制)
    private FlowModel model;
    private FlowProcessInstance flowProcessInstance;
    private FlowTask startTask;
    private StartNode startNode;
    private FlowComment flowComment;

    @Override
    public FlowProcessInstance execute() {
        genFlowProcessDefinitionRef();    //生成流程版本

        createProcessInstance();          //创建流程实例

        createFlowComment();              //创建发起流程的评论信息

        publishStartProcessBeforeEvent(); //发布流程启动前的事件

        createTaskInstance();             //创建流程任务实例

        passStartTask();                  //从开始节点向下自动流转

        forFlowUserDone(formId);          //处理用户的待办

        afterChangeFlowUserTask(flowProcessInstance); //触发创建用户任务的事件

        autoComplete(flowProcessInstance, formModel, userManualSelect); //触发自动审批完成
        getEngine().getFlowActionLogService().logForStartProcess(flowProcessInstance);
        return flowProcessInstance;
    }

    private void createFlowComment() {
        flowComment = FlowUtils.createFlowComment(this.flowProcessInstance);
        getEngine().getFlowCommentService().save(flowComment);
    }

    private void passStartTask() {
        PassTaskContext context = PassTaskContext.builder()
                .variablesContainer(getVariablesContainer(getFormTypeId(), getFormId(), flowProcessInstance, getFormModel()))
                .flowTaskId(startTask.getId())
                .formId(getFormId())
                .formTypeId(getFormTypeId())
                .flowProcessInstanceId(this.getFlowProcessInstance().getId())
                .userManualSelect(userManualSelect)
                .formModel(formModel)
                .build();
        passTask(context);
    }

    private void createTaskInstance() {
        startTask = FlowUtils.createStartTask(flowProcessInstance);
        getEngine().getFlowTaskService().save(startTask);
    }

    private void createProcessInstance() {
        if (CommonUtils.isEmpty(applyBy)) {
            applyBy = getEngine().getCurrentUser();
        }
        flowProcessInstance = FlowUtils.newInstanceForModel(model = getModel(flowProcessDefinitionRef), this);
        getEngine().getFlowProcessInstanceService().save(flowProcessInstance);
        executeProcessTitleFormulaIfNecessary();
        saveProcessTitle();
        startNode = model.getStart();
    }

    private void executeProcessTitleFormulaIfNecessary() {
        if (!CommonUtils.isEmpty(model.getTitleFormula()) && CommonUtils.isEmpty(processTitle)) {
            processTitle = getEngine().executeScript(getEngine().getScriptCommandBuilder().newStringCommand()
                    .scriptText(model.getTitleFormula())
                    .variablesContainer(getVariablesContainer(this.formTypeId, this.formId, flowProcessInstance, this.formModel))
                    .build()).getResult();
        }
    }

    private void saveProcessTitle() {
        flowProcessInstance.setProcessTitle(processTitle);
        if (!CommonUtils.isEmpty(processTitle)) {
            getEngine().getFlowProcessInstanceService().lambdaUpdate()
                    .set(FlowProcessInstance::getProcessTitle, flowProcessInstance.getProcessTitle())
                    .eq(FlowProcessInstance::getId, flowProcessInstance.getId())
                    .update();
        }
    }

    private void publishStartProcessBeforeEvent() {
        publishStartProcessBeforeEvent(this);
    }

    private void genFlowProcessDefinitionRef() {
        if (!CommonUtils.isEmpty(userManualSelect) && !CommonUtils.isEmpty(userManualSelect.getFlowProcessDefinitionRefId())) {
            flowProcessDefinitionRef = getEngine().getFlowProcessDefinitionRefService().getById(userManualSelect.getFlowProcessDefinitionRefId());
            CommonUtils.isEmptyThrow(flowProcessDefinitionRef, new RuntimeException("找不到流程引用：" + userManualSelect.getFlowProcessDefinitionRefId()));
            processDefinitionId = flowProcessDefinitionRef.getId();
            model = getModel(flowProcessDefinitionRef);
            return;
        }
        FlowProcessDefinition flowProcessDefinition = loadFlowProcessDefinitionNotNull(processDefinitionId);
        List<FlowProcessDefinitionRef> refs = getEngine().getFlowProcessDefinitionRefService().lambdaQuery()
                .eq(FlowProcessDefinitionRef::getModelId, processDefinitionId)
                .eq(FlowProcessDefinitionRef::getVersion, flowProcessDefinition.getVersion()).list();
        if (refs.isEmpty()) {
            this.flowProcessDefinitionRef = createFlowProcessDefinitionRef(flowProcessDefinition);
        } else {
            this.flowProcessDefinitionRef = refs.get(0);
        }
        this.processDefinitionId = this.flowProcessDefinitionRef.getId();
    }

    private FlowProcessDefinitionRef createFlowProcessDefinitionRef(FlowProcessDefinition flowProcessDefinition) {
        FlowProcessDefinitionRef flowProcessDefinitionRef = new FlowProcessDefinitionRef();
        flowProcessDefinitionRef.setModelId(flowProcessDefinition.getId());
        flowProcessDefinitionRef.setModel(flowProcessDefinition.getModel());
        flowProcessDefinitionRef.setVersion(flowProcessDefinition.getVersion());

        getEngine().getFlowProcessDefinitionRefService().save(flowProcessDefinitionRef);
        return flowProcessDefinitionRef;
    }

    private FlowProcessDefinition loadFlowProcessDefinitionNotNull(String processDefinitionId) {
        FlowProcessDefinition flowProcessDefinition = getEngine().getFlowProcessDefinitionService().getById(processDefinitionId);
        if (Objects.isNull(flowProcessDefinition))
            throw new RuntimeException("没有找到流程定义: " + processDefinitionId);
        return flowProcessDefinition;
    }
}
